﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using DevExpress.Export.Xl;
using System.IO;
using System.Linq;
using OfficeOpenXml;
using OfficeOpenXml.DataValidation;
using ShieldTunnel2;

namespace ConsoleTest
{
	class Program
	{
		static void Main(string[] args)
		{
			//var s = string.Empty;
			//Debug.Assert(string.IsNullOrWhiteSpace(s));

			//Test5();

			var A = new double[,]
			{
				//{1d, 1d / 5, 1d / 3},
				//{5d, 1d, 3d},
				//{3d, 1d / 3, 1d}
				{1, 5, 7, 1, 1d / 3},
				{1d / 5, 1, 2, 1d / 5, 1d / 8},
				{1d / 7, 1d / 2, 1, 1d / 7, 1d / 9},
				{1, 5, 7, 1, 1d / 3},
				{3, 8, 9, 3, 1},
			};
			var w = RiskEval.GetWeightVector(A);
			var cr = RiskEval.GetConsistenceRatio(A);

			//
			Console.WriteLine("Done!");
			//Console.ReadKey();
		}

		public static void Test1()
		{
			//
			// Experts
			//
			var experts = new[]
			{
				new Expert("专家1", new[] {4, 3, 15, 12, 12, 18}),
				new Expert("专家2", new[] {6, 2, 20, 9, 8, 12}),
				new Expert("专家3", new[] {8, 4, 10, 12, 16, 30}),
				new Expert("专家4", new[] {6, 3, 10, 12, 12, 12}),
			};
			//var expertWeight = GetExpertWeight(experts);


			//
			// 
			//
			var riskEvents = new[]
			{
				new RiskEvent("工作井施工风险U1", 1, -1, new[] {7, 8, 7, 6}),
				new RiskEvent("盾构始发前准备工作风险U2", 2, -1, new[] {7, 8, 7, 6}),
				new RiskEvent("盾构始发风险U3", 3, -1, new[] {6, 5, 4, 5}),
			};
			var scores = riskEvents.Select(re => GetScoreBy19ScaleMethod(re.WeightScores)).ToArray();

			//var weights = GetWeightsByImportanceComparison(scores);

			//
			//
			//
			var expertWeights = new[]
			{
				0.1184, 0.0987, 0.0895, 0.1079, 0.0829,
				0.1039, 0.0961, 0.1118, 0.0908, 0.1000
			};
			var levels = new[]
			{
				5, 5, 3, 2, 4, 3, 6, 4, 4, 3
			};
			var (a, b, c, d) = GetFuzzySetCoefficient(expertWeights, levels);
			var fps = GetFuzzyProbability(a, b, c, d);
			var ffr = GetFuzzyOccurenceProbability(fps);

			var riskLevel = GetRiskEvenRiskLevel(ffr);
			var lostLevel = GetLostEvenLostLevel(ffr);
		}

		public static void Test2()
		{
			var teacher = new Person()
			{
				Name = "Jack",
				Age = 18,
				Friends = new[]
				{
					new Person() {Name = "Ann", Age = 17},
					new Person() {Name = "Jane", Age = 20},
					new Person() {Name = "Mike", Age = 27},
				}
			};
			var filePath = @"D:\person.json";
			var text = JsonConvert.SerializeObject(teacher, Formatting.Indented);
			using (var fs = File.CreateText(filePath))
			{
				fs.Write(text);
			}

			string myText;
			using (var fs = File.OpenText(filePath))
			{
				myText = fs.ReadToEnd();
			}
			var myTeacher = JsonConvert.DeserializeObject<Person>(myText);
			Debug.Assert(teacher.Friends.Length == myTeacher.Friends.Length);
		}

		public static void Test3()
		{
			// Create an exporter instance. 
			var exporter = XlExport.CreateExporter(XlDocumentFormat.Xlsx);

			// Create the FileStream object with the specified file path. 
			using (var stream = new FileStream("Document.xlsx", FileMode.Create, FileAccess.ReadWrite))
			{
				// Create a new document and begin to write it to the specified stream. 
				using (var document = exporter.CreateDocument(stream))
				{
					// Add a new worksheet to the document. 
					using (var sheet = document.CreateSheet())
					{
						// Specify the worksheet name.
						sheet.Name = "Sales report";

						// Create the first column and set its width. 
						using (var column = sheet.CreateColumn())
						{
							column.WidthInPixels = 100;
						}

						// Create the second column and set its width.
						using (var column = sheet.CreateColumn())
						{
							column.WidthInPixels = 250;
						}

						// Create the third column and set the specific number format for its cells.
						using (var column = sheet.CreateColumn())
						{
							column.WidthInPixels = 100;
							column.Formatting = new XlCellFormatting();
							column.Formatting.NumberFormat = @"_([$$-409]* #,##0.00_);_([$$-409]* \(#,##0.00\);_([$$-409]* ""-""??_);_(@_)";
						}

						// Specify cell font attributes.
						var cellFormatting = new XlCellFormatting();
						cellFormatting.Font = new XlFont();
						cellFormatting.Font.Name = "Century Gothic";
						cellFormatting.Font.SchemeStyle = XlFontSchemeStyles.None;

						// Specify formatting settings for the header row.
						var headerRowFormatting = new XlCellFormatting();
						headerRowFormatting.CopyFrom(cellFormatting);
						headerRowFormatting.Font.Bold = true;
						headerRowFormatting.Font.Color = XlColor.FromTheme(XlThemeColor.Light1, 0.0);
						headerRowFormatting.Fill = XlFill.SolidFill(XlColor.FromTheme(XlThemeColor.Accent2, 0.0));

						// Create the header row.
						using (var row = sheet.CreateRow())
						{
							using (var cell = row.CreateCell())
							{
								cell.Value = "Region";
								cell.ApplyFormatting(headerRowFormatting);
							}
							using (var cell = row.CreateCell())
							{
								cell.Value = "Product";
								cell.ApplyFormatting(headerRowFormatting);
							}
							using (var cell = row.CreateCell())
							{
								cell.Value = "Sales";
								cell.ApplyFormatting(headerRowFormatting);
							}
						}

						// Generate data for the sales report.
						var products = new string[]
							{"Camembert Pierrot", "Gorgonzola Telino", "Mascarpone Fabioli", "Mozzarella di Giovanni"};
						var amount = new int[] {6750, 4500, 3550, 4250, 5500, 6250, 5325, 4235};
						for (var i = 0; i < 8; i++)
						{
							using (var row = sheet.CreateRow())
							{
								using (var cell = row.CreateCell())
								{
									cell.Value = (i < 4) ? "East" : "West";
									cell.ApplyFormatting(cellFormatting);
								}
								using (var cell = row.CreateCell())
								{
									cell.Value = products[i % 4];
									cell.ApplyFormatting(cellFormatting);
								}
								using (var cell = row.CreateCell())
								{
									cell.Value = amount[i];
									cell.ApplyFormatting(cellFormatting);
								}
							}
						}

						// Enable AutoFilter for the created cell range.
						sheet.AutoFilterRange = sheet.DataRange;

						// Specify formatting settings for the total row.
						var totalRowFormatting = new XlCellFormatting();
						totalRowFormatting.CopyFrom(cellFormatting);
						totalRowFormatting.Font.Bold = true;
						totalRowFormatting.Fill = XlFill.SolidFill(XlColor.FromTheme(XlThemeColor.Accent5, 0.6));

						// Create the total row.
						using (var row = sheet.CreateRow())
						{
							using (var cell = row.CreateCell())
							{
								cell.ApplyFormatting(totalRowFormatting);
							}
							using (var cell = row.CreateCell())
							{
								cell.Value = "Total amount";
								cell.ApplyFormatting(totalRowFormatting);
								cell.ApplyFormatting(XlCellAlignment.FromHV(XlHorizontalAlignment.Right, XlVerticalAlignment.Bottom));
							}
							using (var cell = row.CreateCell())
							{
								// Add values in the cell range C2 through C9 using the SUBTOTAL function. 
								cell.SetFormula(XlFunc.Subtotal(XlCellRange.FromLTRB(2, 1, 2, 8), XlSummary.Sum, true));
								cell.ApplyFormatting(totalRowFormatting);
							}
						}
					}
				}
			}
			// Open the XLSX document using the default application.
			Process.Start("Document.xlsx");
		}

		public static void Test4()
		{
			var fileName = "Document.xlsx";
			var fi = new FileInfo(fileName);
			if (fi.Exists)
			{
				fi.Delete();
				fi = new FileInfo(fileName);
			}

			using (var excelPackage = new ExcelPackage(fi))
			{
				var sheet = excelPackage.Workbook.Worksheets.Add("list values");

				sheet.Cells[1, 1, 1, 2].Merge = true;
				sheet.Cells[2, 1].Value = "您所在的单位：";

				// add a validation and set values
				var validation = sheet.DataValidations.AddListValidation("B2");
				validation.ShowErrorMessage = true;
				validation.ErrorStyle = ExcelDataValidationWarningStyle.warning;
				validation.ErrorTitle = "An invalid value was entered";
				validation.Error = "Select a value from the list";
				for (var i = 1; i <= 5; i++)
				{
					validation.Formula.Values.Add(i.ToString());
				}
				Console.WriteLine("Added sheet for list validation with values");

				excelPackage.SaveAs(fi);
			}


			/*
			var exporter = XlExport.CreateExporter(XlDocumentFormat.Xlsx);

			using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
			using (var document = exporter.CreateDocument(stream))
			using (var sheet = document.CreateSheet())
			{
				sheet.Name = "Sales report";

				using (var column = sheet.CreateColumn())
				{
					column.WidthInPixels = 100;
				}

				using (var column = sheet.CreateColumn())
				{
					column.WidthInPixels = 250;
				}

				using (var row = sheet.CreateRow())
				{
					using (var cell = row.CreateCell())
					{
						cell.Value = "您所在的单位：";
					}
					row.CreateCell();
					row.CreateCell();
				}

				var validation = new XlDataValidation();
				validation = new XlDataValidation();
				validation.Ranges.Add(XlCellRange.FromLTRB(1, 0, 1, 0));
				validation.Type = XlDataValidationType.List;
				validation.Operator = XlDataValidationOperator.Between;
				//validation.Criteria1 = XlCellRange.FromLTRB(6, 1, 6, 4).AsAbsolute();
				//validation.Criteria1 = "管理单位,设计单位,施工单位";
				//validation.Criteria1 = 
				//validation.Criteria1 = XlValueObject.FromObject("管理单位,设计单位,施工单位");
				//validation.ListValues = new[] {"管理单位", "设计单位"}.Cast<XlVariantValue>();
				validation.ListValues.Add("管理单位");
				validation.ListValues.Add("设计单位");
				validation.ListValues.Add("施工单位");
				validation.ListValues.Add("科研单位");
				validation.Criteria1 = "A,BB,CCCD";

				sheet.DataValidations.Add(validation);
			}
			*/

			// Open the XLSX document using the default application.
			Process.Start(fileName);
		}

		public static void Test5()
		{
			var filePath = @"D:\Code\UWP\TunnelRiskAssessment\ShieldTunnel2\bin\Debug\AppData\专家调查表_秦俊杰.xlsx";
			var fi = new FileInfo(filePath);
			Debug.Assert(fi.Exists);

			bool IsEmptyCell(ExcelRange cell) => string.IsNullOrWhiteSpace(cell.Value?.ToString());

			bool IsMergedCell(ExcelRange cell) => cell.Merge;

			bool IsMergedContentCell(ExcelWorksheet s, int r, int c)
			{
				var cell = s.Cells[r, c];
				return IsMergedCell(cell) && (!IsEmptyCell(cell));
			}

			bool IsMergedEmptyCell(ExcelWorksheet s, int r, int c)
			{
				var cell = s.Cells[r, c];
				return IsMergedCell(cell) && IsEmptyCell(cell);
			}

			bool IsNomergedEmptyCell(ExcelWorksheet s, int r, int c)
			{
				var cell = s.Cells[r, c];
				return (!IsMergedCell(cell)) && IsEmptyCell(cell);
			}

			using (var excelPackage = new ExcelPackage(fi))
			{
				var sheet = excelPackage.Workbook.Worksheets[2];

				Debug.Assert(IsMergedContentCell(sheet, 2, 1));
				Debug.Assert(IsMergedEmptyCell(sheet, 3, 1));
				Debug.Assert(IsMergedEmptyCell(sheet, 4, 1));
				Debug.Assert(IsNomergedEmptyCell(sheet, 11, 1));
				Debug.Assert(IsNomergedEmptyCell(sheet, 12, 1));

				Debug.Assert(IsMergedContentCell(sheet, 7, 9));
				Debug.Assert(IsMergedEmptyCell(sheet, 8, 9));
				Debug.Assert(IsMergedContentCell(sheet, 10, 9));
				Debug.Assert(IsMergedContentCell(sheet, 11, 9));
				Debug.Assert(IsNomergedEmptyCell(sheet, 126, 9));
				Debug.Assert(IsNomergedEmptyCell(sheet, 127, 9));
			}
		}

		#region Helper Methods

		/// <summary>
		/// 计算专家权重系数
		/// </summary>
		/// <param name="experts"></param>
		/// <returns></returns>
		public static IList<double> GetExpertWeight(IList<Expert> experts)
		{
			var exportScore = experts.Select(e => e.ItemScores.Sum()).ToArray();
			var totalScore = exportScore.Sum();
			var weights = exportScore.Select(es => ((double) es) / totalScore).ToArray();
			for (var i = 0; i < experts.Count; i++)
			{
				experts[i].Weight = weights[i];
			}
			Debug.Assert(Math.Abs(weights.Sum() - 1d) < 1e-6);
			return weights;
		}

		/// <summary>
		/// 根据1-9标度法和专家打分（1-9），按80%累计频率计算分值
		/// </summary>
		/// <param name="scores"></param>
		/// <returns></returns>
		public static double GetScoreBy19ScaleMethod(IList<int> scores)
		{
			Debug.Assert(scores.All(s => 1 <= s && s <= 9));
			var freqs = Enumerable.Range(1, 9)
				.Select(i => (Score: i, Freq: (double) scores.Count(s => s == i) / scores.Count));
			var orderedFreqs = freqs.OrderByDescending(f => f.Freq * 100d + f.Score);

			var totalScore = 0d;
			var totalFreq = 0d;
			foreach (var freq in orderedFreqs)
			{
				totalScore += freq.Score * freq.Freq;

				totalFreq += freq.Freq;
				if (totalFreq >= 0.8)
					break;
			}

			return totalScore;
		}

		public static IList<double> GetWeightsByImportanceComparison(IList<double> scores)
		{
			var df = scores.Max() - scores.Min();
			var boundaries = new[] {-0.1 * df, 0, 0.25 * df, 0.5 * df, 0.75 * df, df};
			var levels = new[] {1d, 3d, 5d, 7d, 9d};
			var n = scores.Count;

			//
			// 计算判断矩阵A
			//
			var a = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					var ds = scores[i] - scores[j];
					var level = MatchLevel2(Math.Abs(ds), boundaries, levels);
					Debug.Assert(1d <= level && level <= 9d);
					a[i, j] = ds >= 0 ? level : (1d / level);
				}
			}

			//
			// 调整判断矩阵A，以符合一致性检验
			//
			var ap = GetAdjustedComparisonMatrix(a);

			//
			// 计算相对权重W
			//
			var w = GetWeightVector(ap);
			return w;
		}

		/// <summary>
		/// 调整判断矩阵A，以符合一致性检验
		/// </summary>
		/// <param name="a"></param>
		/// <returns></returns>
		public static double[,] GetAdjustedComparisonMatrix(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);
			var ap = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					const double eps = 1e-6;
					Debug.Assert(a[i, j] > 0 && Math.Abs(a[i, j] * a[j, i] - 1d) < eps);
					ap[i, j] = a[i, j];
				}
			}

			while (true)
			{
				var cr = GetConsistenceRatio(ap);
				if (cr <= 0.1)
					break;

				var c = GetInduceMatrix(ap);

				int k = 0, l = 0;
				var dmax = 0d;
				for (var i = 0; i < n; i++)
				{
					for (var j = 0; j < n; j++)
					{
						if (Math.Abs(c[i, j] - 1d) > dmax)
						{
							dmax = Math.Abs(c[i, j] - 1d);

							k = i;
							l = j;
						}
					}
				}

				Debug.Assert(Math.Abs(c[k, l] - 1d) > 0);
				if (c[k, l] > 1)
				{
					if (ap[k, l] > 1d)
					{
						ap[k, l] = ap[k, l] - 1;
					}
					else
					{
						ap[k, l] = 1d / (1d / ap[k, l] + 1d);
					}
				}
				else
				{
					if (ap[k, l] > 1d)
					{
						ap[k, l] = ap[k, l] + 1;
					}
					else
					{
						ap[k, l] = 1d / (1d / ap[k, l] - 1d);
					}
				}
				ap[l, k] = 1d / ap[k, l];
			}
			return ap;
		}

		/// <summary>
		/// 计算判断矩阵A的权重向量W
		/// </summary>
		/// <param name="a"></param>
		/// <returns></returns>
		public static double[] GetWeightVector(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);

			// 判断矩阵A各行元素之积
			var b = Enumerable.Repeat(1d, n).ToArray();
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					b[i] *= a[i, j];
				}
			}

			// bi的n次方根P
			var p = b.Select(x => Math.Pow(x, 1d / n)).ToArray();

			// 将P正规化，求权重向量W
			var ps = p.Sum();
			var w = p.Select(x => x / ps).ToArray();
			return w;
		}

		/// <summary>
		/// 计算判断矩阵A的一致性比例CR
		/// </summary>
		/// <param name="a"></param>
		/// <returns></returns>
		public static double GetConsistenceRatio(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);

			// 计算相对权重W
			var w = GetWeightVector(a);

			// 判断矩阵A的最大特征根
			var lmax = a.MatrixMultiply(w)
				           .VectorDotOperation(w, (x, y) => x / y)
				           .Sum() / n;

			// 一致性指标CI
			var ci = (lmax - n) / (n - 1);

			// 一致性比例
			var ris = new[]
			{
				0d, 0d, 0.52, 0.89, 1.12, 1.26,
				1.36, 1.41, 1.46, 1.49, 1.52, 1.54
			};
			Debug.Assert(n < ris.Length);
			var ri = ris[n - 1];
			var cr = ci / ri;
			return cr;
		}

		/// <summary>
		/// 计算判断矩阵A的诱导矩阵C
		/// </summary>
		/// <param name="a"></param>
		/// <returns></returns>
		public static double[,] GetInduceMatrix(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);

			var b = new double[n, n];
			for (var j = 0; j < n; j++)
			{
				var aj = 0d;
				for (var i = 0; i < n; i++)
				{
					aj += a[i, j];
				}

				for (var i = 0; i < n; i++)
				{
					b[i, j] = a[i, j] / aj;
				}
			}

			var w = new double[n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					w[i] += b[i, j];
				}
				w[i] = w[i] / n;
			}

			var c = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					c[i, j] = b[i, j] / w[i];
				}
			}

			return c;
		}

		/// <summary>
		/// 求平均模糊函数中的4个系数
		/// </summary>
		/// <param name="expertWeights"></param>
		/// <param name="levels"></param>
		/// <returns></returns>
		public static (double a, double b, double c, double d)
			GetFuzzySetCoefficient(IList<double> expertWeights, IList<int> levels)
		{
			Debug.Assert(expertWeights.Count == levels.Count);
			Debug.Assert(Math.Abs(expertWeights.Sum() - 1d) < 1e-6);
			Debug.Assert(levels.All(l => 1 <= l && l <= 7));

			var x1s = new[] {0d, 0.1, 0.2, 0.4, 0.5, 0.7, 0.8};
			var x2s = new[] {0d, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1};
			var x3s = new[] {0.2, 0.3, 0.5, 0.6, 0.8, 0.9, 1d};
			var x4s = new[] {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0d};

			var x1 = expertWeights.Zip(levels, (w, l) => w * x1s[l - 1])
				.Sum();
			var x2 = expertWeights.Zip(levels, (w, l) => w * x2s[l - 1])
				.Sum();
			var x3 = expertWeights.Zip(levels, (w, l) => w * x3s[l - 1])
				.Sum();
			var x4 = expertWeights.Zip(levels, (w, l) => w * x4s[l - 1])
				.Sum();
			return (x1, x2, x3, x4);
		}

		/// <summary>
		/// 计算模糊数的模糊可能性值FPS
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <param name="c"></param>
		/// <param name="d"></param>
		/// <returns></returns>
		public static double GetFuzzyProbability(double a, double b, double c, double d)
		{
			Debug.Assert(a < c && b <= 0.1 && d <= 0.1);
			var y1 = 1 - (a + b) / (1 + b);
			var y2 = 1 - (c - d) / (1 - d);
			var y3 = a / (1 - b);
			var y4 = c / (1 + d);
			var fpsl = Math.Max(y1, y2);
			var fpsr = Math.Max(y3, y4);
			var fps = (fpsr + 1d - fpsl) / 2d;
			return fps;
		}

		/// <summary>
		/// 计算模糊发生概率FFR
		/// </summary>
		/// <param name="fps"></param>
		/// <returns></returns>
		public static double GetFuzzyOccurenceProbability(double fps)
		{
			if (Math.Abs(fps) < 1e-8)
				return 0;

			var k = Math.Pow((1 - fps) / fps, 1d / 3d) * 2.301;
			var ffr = 1d / Math.Pow(10d, k);
			return ffr;
		}

		public static int GetRiskEvenRiskLevel(double ffr)
		{
			Debug.Assert(ffr >= 0);
			var boundaries = new[] {0d, 1e-4, 1e-3, 1e-2, 1e-1, 1d};
			var levels = new[] {1, 2, 3, 4, 5};
			var level = MatchLevel(ffr, boundaries, levels);
			return level;
		}

		public static int GetLostEvenLostLevel(double ffr)
		{
			Debug.Assert(ffr >= 0);
			var boundaries = new[] {0d, 1e-4, 1e-3, 1e-2, 1e-1, 1d};
			var levels = new[] {1, 2, 3, 4, 5};
			var level = MatchLevel(ffr, boundaries, levels);
			return level;
		}

		public static int GetAcceptanceLevel(int riskLevel, int lostLevel)
		{
			Debug.Assert((1 <= riskLevel && riskLevel <= 5)
			             && (1 <= lostLevel && lostLevel <= 5));
			var levelTable = new[]
			{
				new[] {11, 21, 12, 13},
				new[] {31, 22, 32, 23, 14, 15},
				new[] {41, 51, 42, 33, 24, 25},
				new[] {52, 43, 53, 34, 44, 35},
				new[] {54, 45, 55},
			};
			var acceptanceLevel = levelTable.Select((ls, i) => new {Levels = ls, AcceptanceLevel = i + 1})
				.First(ls => ls.Levels.Contains(riskLevel * 10 + lostLevel))
				.AcceptanceLevel;
			return acceptanceLevel;
		}

		/// <summary>
		/// 根据分界点确定分级
		/// </summary>
		/// <typeparam name="TBoundary"></typeparam>
		/// <typeparam name="TLevel"></typeparam>
		/// <param name="value"></param>
		/// <param name="boundaries"></param>
		/// <param name="levels"></param>
		/// <returns></returns>
		public static TLevel MatchLevel2<TBoundary, TLevel>(TBoundary value,
			IList<TBoundary> boundaries, IList<TLevel> levels)
			where TBoundary : IComparable
		{
			Debug.Assert(boundaries != null && levels != null && levels.Count >= 1
			             && boundaries.Count == levels.Count + 1);
			Debug.Assert(value.CompareTo(boundaries.First()) >= 0
			             && value.CompareTo(boundaries.Last()) <= 0);

			var level = levels.First();
			for (var i = 0; i < levels.Count; i++)
			{
				var lower = boundaries[i];
				var upper = boundaries[i + 1];
				Debug.Assert(lower.CompareTo(upper) < 0);
				if (0 < value.CompareTo(lower) && value.CompareTo(upper) <= 0)
				{
					level = levels[i];
					break;
				}
			}
			return level;
		}

		/// <summary>
		/// 根据分界点确定分级
		/// </summary>
		/// <typeparam name="TBoundary"></typeparam>
		/// <typeparam name="TLevel"></typeparam>
		/// <param name="value"></param>
		/// <param name="boundaries"></param>
		/// <param name="levels"></param>
		/// <returns></returns>
		public static TLevel MatchLevel<TBoundary, TLevel>(TBoundary value,
			IList<TBoundary> boundaries, IList<TLevel> levels)
			where TBoundary : IComparable
		{
			Debug.Assert(boundaries != null && levels != null && levels.Count >= 1
			             && boundaries.Count == levels.Count + 1);
			Debug.Assert(value.CompareTo(boundaries.First()) >= 0
			             && value.CompareTo(boundaries.Last()) <= 0);

			var level = levels.Last();
			for (var i = 0; i < levels.Count; i++)
			{
				var lower = boundaries[i];
				var upper = boundaries[i + 1];
				Debug.Assert(lower.CompareTo(upper) < 0);
				if (0 <= value.CompareTo(lower) && value.CompareTo(upper) < 0)
				{
					level = levels[i];
					break;
				}
			}
			return level;
		}

		#endregion
	}

	public class Person
	{
		public string Name { get; set; }
		public int Age { get; set; }
		public Person[] Friends { get; set; }
	}

	/// <summary>
	/// 专家信息
	/// </summary>
	public class Expert
	{
		public string Name { get; set; }

		public IList<int> ItemScores { get; set; }
		public double Weight { get; set; }

		public Expert(string name, IList<int> itemScores)
		{
			this.Name = name;
			this.ItemScores = itemScores;
		}
	}

	/// <summary>
	/// 风险事件
	/// </summary>
	public class RiskEvent
	{
		public string Name { get; set; }
		public int Id { get; set; }
		public int ParentId { get; set; }

		public IList<int> WeightScores { get; set; }
		public double Weight { get; set; }

		public RiskEvent(string name, int id, int parentId, IList<int> weightScores)
		{
			Debug.Assert(id >= 0 && parentId >= -1 && id != parentId);
			this.Name = name;
			this.Id = id;
			this.ParentId = id;
			this.WeightScores = weightScores;
		}
	}

	/// <summary>
	/// 致险因子
	/// </summary>
	public class RiskFactor
	{
		public string Name { get; set; }
		public int Id { get; set; }

		public IList<int> WeightScores { get; set; }
		public double Weight { get; set; }

		public IList<int> ProbabilityScores { get; set; }
		public double Probability { get; set; }

		public IList<int> LifeLostScores { get; set; }
		public IList<int> FinancialLostScores { get; set; }
		public IList<int> TimeLostScores { get; set; }
		public IList<int> StuctureLostScores { get; set; }
	}

	public class LostFactor
	{
		public string Name { get; set; }

		public IList<int> WeightScores { get; set; }
		public double Weight { get; set; }
	}

	#region Helper Classes

	public static class MatrixOperation
	{
		public static double[] MatrixMultiply(this double[,] a, double[] b)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1)
			             && a.GetLength(0) == b.Length);
			var c = new double[b.Length];
			for (var i = 0; i < b.Length; i++)
			{
				for (var j = 0; j < b.Length; j++)
				{
					c[i] += a[i, j] * b[j];
				}
			}
			return c;
		}

		public static double[] VectorDotOperation(this double[] a, double[] b,
			Func<double, double, double> f)
		{
			Debug.Assert(a.Length == b.Length);
			var c = new double[a.Length];
			for (var i = 0; i < a.Length; i++)
			{
				c[i] = f(a[i], b[i]);
			}
			return c;
		}
	}

	#endregion
}
